﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AnaxibiaStudios.Smug.Internal;

namespace SmugTest
{
  [TestClass]
  public class MethodInfoExTest
  {
    interface I_0
    {
      int Foo();
    }

    interface I_1
    {
      int Foo();
    }

    interface I_2
    {
      double Foo();
    }

    interface I_3
    {
      int Foo(int i);
    }

    class C_0
    {
      public int Foo() { return 1; }
    }

    class C_1
    {
      protected virtual int Foo() { return 2; }
    }

    [TestMethod]
    public void MethodConflictTest()
    {
      var i0foo = GetFoo<I_0>();
      var i1foo = GetFoo<I_1>();
      var i2foo = GetFoo<I_2>();
      var i3foo = GetFoo<I_3>();
      var c0foo = GetFoo<C_0>();
      var c1foo = GetFoo<C_1>();

      Assert.AreEqual(PreexistingType.Exact, i0foo.Hit(i1foo));
      Assert.AreEqual(PreexistingType.Conflicting, i0foo.Hit(i2foo));
      Assert.AreEqual(PreexistingType.New, i0foo.Hit(i3foo));
      Assert.AreEqual(PreexistingType.Exact, i0foo.Hit(c0foo));
      Assert.AreEqual(PreexistingType.Conflicting, i0foo.Hit(c1foo));

      Assert.AreEqual(PreexistingType.Exact, i1foo.Hit(i0foo));
      Assert.AreEqual(PreexistingType.Conflicting, i1foo.Hit(i2foo));
      Assert.AreEqual(PreexistingType.New, i1foo.Hit(i3foo));
      Assert.AreEqual(PreexistingType.Exact, i1foo.Hit(c0foo));
      Assert.AreEqual(PreexistingType.Conflicting, i1foo.Hit(c1foo));

      Assert.AreEqual(PreexistingType.Conflicting, i2foo.Hit(i0foo));
      Assert.AreEqual(PreexistingType.Conflicting, i2foo.Hit(i1foo));
      Assert.AreEqual(PreexistingType.New, i2foo.Hit(i3foo));
      Assert.AreEqual(PreexistingType.Conflicting, i2foo.Hit(c0foo));
      Assert.AreEqual(PreexistingType.Conflicting, i2foo.Hit(c1foo));

      Assert.AreEqual(PreexistingType.New, i3foo.Hit(i0foo));
      Assert.AreEqual(PreexistingType.New, i3foo.Hit(i1foo));
      Assert.AreEqual(PreexistingType.New, i3foo.Hit(i2foo));
      Assert.AreEqual(PreexistingType.New, i3foo.Hit(c0foo));
      Assert.AreEqual(PreexistingType.New, i3foo.Hit(c1foo));

      Assert.AreEqual(PreexistingType.Exact, c0foo.Hit(i0foo));
      Assert.AreEqual(PreexistingType.Exact, c0foo.Hit(i1foo));
      Assert.AreEqual(PreexistingType.Conflicting, c0foo.Hit(i2foo));
      Assert.AreEqual(PreexistingType.New, c0foo.Hit(i3foo));
      Assert.AreEqual(PreexistingType.Conflicting, c0foo.Hit(c1foo));

      Assert.AreEqual(PreexistingType.Conflicting, c1foo.Hit(i0foo));
      Assert.AreEqual(PreexistingType.Conflicting, c1foo.Hit(i1foo));
      Assert.AreEqual(PreexistingType.Conflicting, c1foo.Hit(i2foo));
      Assert.AreEqual(PreexistingType.New, c1foo.Hit(i3foo));
      Assert.AreEqual(PreexistingType.Conflicting, c1foo.Hit(c0foo));
    }

    private MethodInfoEx GetFoo<T>() where T : class
    {
      var method = typeof(T).GetMethod("Foo", Constants.Flags);
      return MemberInfoEx.SoloWrap(new SourceBuilder(typeof(T)), method);
    }

    // ref vs out => conflict
    // ref/out vs normal => new
    interface I_4
    {
      void Foo(int i);
    }

    interface I_5
    {
      void Foo(ref int i);
    }

    interface I_6
    {
      void Foo(out int i);
    }

    [TestMethod]
    public void MethodRefConflictTest()
    {
      var i4foo = GetFoo<I_4>();
      var i5foo = GetFoo<I_5>();
      var i6foo = GetFoo<I_6>();

      Assert.AreEqual(PreexistingType.New, i4foo.Hit(i5foo), "normal hit ref");
      Assert.AreEqual(PreexistingType.New, i4foo.Hit(i6foo), "normal hit out");

      Assert.AreEqual(PreexistingType.New, i5foo.Hit(i4foo), "ref hit normal");
      Assert.AreEqual(PreexistingType.Conflicting, i5foo.Hit(i6foo), "ref hit out");
    
      Assert.AreEqual(PreexistingType.New, i6foo.Hit(i4foo), "out hit normal");
      Assert.AreEqual(PreexistingType.Conflicting, i6foo.Hit(i5foo), "out hit ref");
    }
  }
}
